package rbac

import (
	"fmt"
	"github.com/gin-gonic/gin"
	"github.com/mojocn/base64Captcha"
	"image/color"
	"net/http"
	"store/libs/cache"
	"store/libs/controller"
	"store/libs/helper"
	"store/libs/log"
	"store/logic/rbac"
	rbacModel "store/models/rbac"
	"time"
)

type Login struct {
	controller.Controller
}

var (
	fontColor = color.RGBA{
		R: 255,
		G: 0,
		B: 0,
		A: 0,
	}
	height        = 40
	width         = 160
	noiseCount    = 12
	length        = 4
	source        = "大小上下左右前后天地春夏秋冬"
	stringSource  = "abcd123458984fjhipqcdbvmnx"
	chineseDriver = base64Captcha.NewDriverChinese(height, width, noiseCount, 0, length, source, &fontColor, []string{"wqy-microhei.ttc"})
	mathDriver    = base64Captcha.NewDriverMath(height, width, noiseCount, 0, &fontColor, []string{"wqy-microhei.ttc"})
	stringDriver  = base64Captcha.NewDriverString(height, width, length, noiseCount, 4, stringSource, &fontColor, []string{"RitaSmith.ttf"})
)

// @Summary 获取验证码
// @Description 获取验证码
// @Tags 登录
// @Accept  application/json,application/xml
// @Produce json,xml
// @Success 200 {object} controller.Response
// @failure 400 {object} controller.Response
// @Router /admin/code [post]
func (this *Login) Code(ctx *gin.Context) {
	var captcha *base64Captcha.Captcha
	switch time.Now().Unix() % 3 {
	case 0:
		captcha = base64Captcha.NewCaptcha(chineseDriver, base64Captcha.DefaultMemStore)
	case 1:
		captcha = base64Captcha.NewCaptcha(mathDriver, base64Captcha.DefaultMemStore)
	case 2:
		captcha = base64Captcha.NewCaptcha(stringDriver, base64Captcha.DefaultMemStore)
	}

	id, b64, err := captcha.Generate()
	if err != nil {
		log.Error(ctx, err.Error())
	}

	this.Response(ctx, http.StatusOK, "ok", struct {
		Id  string `json:"id" xml:"id"`
		B64 string `json:"b64" xml:"b64"`
	}{
		Id:  id,
		B64: b64,
	})
}

// @Summary 登录
// @Description 用户名密码登录
// @Tags 登录
// @Accept  application/json,application/xml
// @Produce json,xml
// @Param  loginInfo   body    rbac.LoginInput     true        "认证信息"
// @Success 200 {object} controller.Response
// @failure 400 {object} controller.Response
// @Router /admin/login [post]
func (this *Login) Login(ctx *gin.Context) {
	input := rbac.LoginInput{}
	err := ctx.ShouldBind(&input)
	if err != nil {
		this.Response(ctx, http.StatusBadRequest, this.FormatError(input, err).Error(), nil)
		return
	}

	if !base64Captcha.DefaultMemStore.Verify(input.Id, input.Code, true) {
		this.Response(ctx, http.StatusBadRequest, "验证码错误", nil)
		return
	}

	admin := rbac.Login(input.UserName, input.Password)
	if admin == nil || admin.Id < 1 {
		this.Response(ctx, http.StatusBadRequest, "登录名或者密码错误", nil)
		return
	}

	info := rbac.LoginInfo{
		Ip:        ctx.ClientIP(),
		Id:        admin.Id,
		AdminName: admin.UserName,
		LoginTime: time.Now().Unix(),
	}

	token := helper.Sha1(helper.ToString(time.Now().UnixNano()))
	cache.Set(token, info, 1800)
	helper.SetCookie(ctx, "auth", token, 3600*24*7)
	this.Response(ctx, http.StatusOK, "ok", struct {
		UserName string `json:"userName" xml:"userName"`
		TrueName string `json:"trueName" xml:"trueName"`
	}{
		UserName: admin.UserName,
		TrueName: admin.TrueName,
	})
}

// @Summary 注销登录
// @Description 注销登录
// @Tags 登录
// @Accept  application/json,application/xml
// @Produce json,xml
// @Success 302
// @Router /admin/login-out [get]
func (this *Login) LoginOut(ctx *gin.Context) {
	token, _ := ctx.Cookie("auth")
	if token != "" {
		cache.Delete(token)
	}
	ctx.Redirect(http.StatusFound, "/web/sign-in.html")
}

/**
 * 检查是否登录
 */
func (this *Login) CheckLogin(ctx *gin.Context) *rbac.LoginInfo {
	token, err := ctx.Cookie("auth")
	if err != nil {
		return nil
	}

	admin := rbac.LoginInfo{}
	cache.Get(token, &admin)
	if admin.Id < 1 {
		return nil
	}

	if admin.Ip != ctx.ClientIP() {
		log.Warn(ctx, helper.Append("ip从", admin.Ip, "变为", ctx.ClientIP(), "，重新登录吧"))
		return nil
	}

	//延长时间
	cache.Set(token, admin, 1800)

	return &admin
}

func (this *Login) GetAdminModel(ctx *gin.Context) (rbacModel.Admin, error) {
	adminInfo := rbacModel.Admin{}

	admin := this.CheckLogin(ctx)
	if admin == nil || admin.Id < 1 {
		return adminInfo, fmt.Errorf("请先登录")
	}

	//设置管理员登录信息
	ctx.Set("admin", admin)

	logic := rbac.AdminLogic{
		Ctx: ctx,
	}

	info, exists := logic.View(map[string]interface{}{
		"id": admin.Id,
	})

	if !exists {
		return adminInfo, fmt.Errorf("您的账户不存在")
	}

	adminInfo, ok := info.(rbacModel.Admin)
	if !ok {
		return adminInfo, fmt.Errorf("您的账户不存在")
	}
	return adminInfo, nil
}

// @Summary 获取管理员权限
// @Description 获取管理员权限
// @Tags 登录
// @Accept  json
// @Produce json
// @Success 200 {object} controller.Response
// @failure 400 {object} controller.Response
// @Router /admin/rights [get]
func (this *Login) GetRights(ctx *gin.Context) {
	admin, err := this.GetAdminModel(ctx)
	if err != nil {
		this.Response(ctx, http.StatusForbidden, err.Error(), nil)
		return
	}

	tree := rbac.GetAllEnableRights(ctx)
	if admin.IsSuperAdmin == 1 {
		this.Response(ctx, http.StatusOK, "ok", tree)
		return
	}

	rightsIds := rbac.GetRightsIdList(ctx, admin.Id)
	if len(rightsIds) < 1 {
		this.Response(ctx, http.StatusForbidden, "您没有权限登录", nil)
		return
	}

	if len(tree.Children) < 1 {
		this.Response(ctx, http.StatusOK, "ok", tree)
		return
	}

	for index := 0; index < len(tree.Children); {
		//Module权限
		moduleId := tree.Children[index].Data.Id
		if _, exists := rightsIds[moduleId]; !exists {
			tree.Children = append(tree.Children[:index], tree.Children[index+1:]...)
			continue
		}

		//Controller权限
		for ind := 0; ind < len(tree.Children[index].Children); {
			controllerId := tree.Children[index].Children[ind].Data.Id
			if _, exists := rightsIds[controllerId]; !exists {
				tree.Children[index].Children = append(tree.Children[index].Children[:ind], tree.Children[index].Children[ind+1:]...)
				continue
			}
			//Action权限
			for i := 0; i < len(tree.Children[index].Children[ind].Children); {
				actionId := tree.Children[index].Children[ind].Children[i].Data.Id
				if _, exists := rightsIds[actionId]; !exists {
					tree.Children[index].Children[ind].Children = append(tree.Children[index].Children[ind].Children[:i], tree.Children[index].Children[ind].Children[i+1:]...)
					continue
				}

				i++
			}
			ind++
		}

		index++
	}

	this.Response(ctx, http.StatusOK, "ok", tree)
}
